package com.luych.toolbox.docker.service.service;

import com.luych.toolbox.common.enums.DockerServerStatus;
import com.luych.toolbox.common.feign.intf.base.service.UserFeign;
import com.luych.toolbox.common.feign.param.docker.service.DockerServerParam;
import com.luych.toolbox.common.feign.view.base.service.UserView;
import com.luych.toolbox.common.feign.view.docker.service.DockerServerView;
import com.luych.toolbox.common.page.PageParam;
import com.luych.toolbox.common.page.PageResult;
import com.luych.toolbox.common.service.base.BaseConvert;
import com.luych.toolbox.common.service.base.BaseService;
import com.luych.toolbox.common.service.exception.HttpException;
import com.luych.toolbox.common.service.page.SpringPageParamAdapter;
import com.luych.toolbox.common.service.page.SpringPageResultAdapter;
import com.luych.toolbox.common.service.tool.Capacity;
import com.luych.toolbox.docker.service.constant.Constant;
import com.luych.toolbox.docker.service.dao.DockerServerDao;
import com.luych.toolbox.docker.service.dao.DockerStatDao;
import com.luych.toolbox.docker.service.entity.DockerServer;
import com.luych.toolbox.docker.service.job.DockerServerHealthCheckJob;
import com.luych.toolbox.docker.service.job.DockerServerStatJob;
import com.luych.toolbox.docker.service.sdk.DockerClient;
import com.luych.toolbox.docker.service.sdk.system.model.DockerInfoResult;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Service
public class DockerServerService extends BaseService<DockerServerView, DockerServerParam, DockerServer>
        implements ApplicationListener<ApplicationReadyEvent> {

    private static final BaseConvert<DockerServerParam, DockerServer> dockerServerParamToEntity = DockerServer::new;
    private static final BaseConvert<DockerServer, DockerServerView> dockerServerEntityToView = DockerServerView::new;

    private final Boolean rescheduleWhenStart;
    private final Scheduler scheduler;
    private final DockerServerDao dockerServerDao;
    private final DockerStatDao dockerStatDao;
    private final UserFeign userFeign;

    @Autowired
    public DockerServerService(@Value("${spring.quartz.rescheduleWhenStart:false}") Boolean rescheduleWhenStart,
                               Scheduler scheduler,
                               DockerServerDao dockerServerDao,
                               DockerStatDao dockerStatDao, UserFeign userFeign) {
        super(dockerServerDao, dockerServerParamToEntity, dockerServerEntityToView);
        this.rescheduleWhenStart = rescheduleWhenStart;
        this.scheduler = scheduler;
        this.dockerServerDao = dockerServerDao;
        this.dockerStatDao = dockerStatDao;
        this.userFeign = userFeign;
    }

    @Override
    protected DockerServerView fill(DockerServerView view) {
        dockerServerDao.findById(view.getId()).map(DockerServer::getOwnerId).map(userFeign::get).ifPresent(view::setOwner);
        if (DockerServerStatus.NORMAL.equals(view.getStatus())) {
            DockerClient dockerClient = new DockerClient(view.getUrl());
            DockerInfoResult dockerInfoResult = dockerClient.system().info();
            view.setDockerVersion(dockerInfoResult.getServerVersion());
            view.setStorageDriver(dockerInfoResult.getDriver());
            view.setLoggingDriver(dockerInfoResult.getLoggingDriver());
            view.setCgroupDriver(dockerInfoResult.getCgroupDriver());
            view.setKernelVersion(dockerInfoResult.getKernelVersion());
            view.setOperatingSystem(dockerInfoResult.getOperatingSystem());
            view.setArchitecture(dockerInfoResult.getArchitecture());
            view.setCpu(dockerInfoResult.getNcpu());
            view.setMemory(dockerInfoResult.getMemTotal());
            view.setMemoryHumanRead(Capacity.convertHumanReadSimple(dockerInfoResult.getMemTotal()));
        }
        return view;
    }

    @Override
    public DockerServerView save(DockerServerParam param) {
        // check owner
        UserView owner = userFeign.get(param.getOwnerId());
        if (owner == null) {
            throw new HttpException(HttpStatus.BAD_REQUEST, Constant.DOCKER_SERVER_SAVE_OWNER_ERROR);
        }
        // save docker server
        DockerServer dockerServerParam = dockerServerParamToEntity.apply(param);
        dockerServerParam.setStatus(DockerServerStatus.WAITING);
        DockerServer dockerServerResult = dockerServerDao.save(dockerServerParam);
        return this.fill(dockerServerEntityToView.apply(dockerServerResult));
    }

    private JobKey getJobKey(String id, Class<? extends Job> clazz) {
        return new JobKey(id, clazz.getName());
    }

    private JobDetail getJobDetail(String id, Class<? extends Job> clazz) {
        Map<String, Object> jobParam = new HashMap<String, Object>() {{
            this.put("id", id);
        }};
        return JobBuilder.newJob(clazz)
                .withIdentity(this.getJobKey(id, clazz))
                .usingJobData(new JobDataMap(jobParam))
                .build();
    }

    private Trigger getJobTrigger(String id, Class<? extends Job> clazz, int second) {
        return TriggerBuilder.newTrigger()
                .withIdentity(id, clazz.getName())
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(second))
                .build();
    }

    @Override
    public DockerServerView create(DockerServerParam param) {
        Assert.notNull(param.getName(), "Param name can't be empty");
        Assert.notNull(param.getUrl(), "Param url can't be empty");
        Assert.notNull(param.getOwnerId(), "Param ownerId can't be empty");

        // check name exists
        if (dockerServerDao.findByName(param.getName()).isPresent()) {
            throw new HttpException(HttpStatus.CONFLICT, Constant.DOCKER_SERVER_SAVE_NAME_USED);
        }

        // save
        DockerServerView view = this.save(param);

        // schedule
        try {
            scheduler.scheduleJob(
                    this.getJobDetail(view.getId(), DockerServerHealthCheckJob.class),
                    this.getJobTrigger(view.getId(), DockerServerHealthCheckJob.class, 600)
            );
            scheduler.scheduleJob(
                    this.getJobDetail(view.getId(), DockerServerStatJob.class),
                    this.getJobTrigger(view.getId(), DockerServerStatJob.class, 5)
            );
        } catch (SchedulerException e) {
            throw new IllegalStateException(e);
        }

        // return
        return view;
    }

    @Override
    public DockerServerView modify(DockerServerParam param) {
        Assert.notNull(param.getId(), "Param id can't be empty");
        Assert.notNull(param.getName(), "Param name can't be empty");
        Assert.notNull(param.getUrl(), "Param url can't be empty");
        Assert.notNull(param.getOwnerId(), "Param ownerId can't be empty");

        // check name exists
        Optional<DockerServer> dataAgentOptOfName = dockerServerDao.findByName(param.getName());
        if (dataAgentOptOfName.isPresent() && !dataAgentOptOfName.get().getId().equals(param.getId())) {
            throw new HttpException(HttpStatus.CONFLICT, Constant.DOCKER_SERVER_SAVE_NAME_USED);
        }

        // save
        DockerServerView view = this.save(param);

        // schedule
        try {
            scheduler.deleteJob(this.getJobKey(view.getId(), DockerServerHealthCheckJob.class));
            scheduler.deleteJob(this.getJobKey(view.getId(), DockerServerStatJob.class));
            scheduler.scheduleJob(
                    this.getJobDetail(view.getId(), DockerServerHealthCheckJob.class),
                    this.getJobTrigger(view.getId(), DockerServerHealthCheckJob.class, 600)
            );
            scheduler.scheduleJob(
                    this.getJobDetail(view.getId(), DockerServerStatJob.class),
                    this.getJobTrigger(view.getId(), DockerServerStatJob.class, 5)
            );
        } catch (SchedulerException e) {
            throw new IllegalStateException(e);
        }

        // return
        return view;
    }

    @Override
    public void remove(String id) {
        dockerStatDao.deleteByServerId(id);
        // schedule
        try {
            scheduler.deleteJob(this.getJobKey(id, DockerServerHealthCheckJob.class));
            scheduler.deleteJob(this.getJobKey(id, DockerServerStatJob.class));
        } catch (SchedulerException e) {
            throw new IllegalStateException(e);
        }

        // delete
        dockerServerDao.deleteById(id);
    }

    public PageResult<DockerServerView> pageByKeyword(PageParam pageParam, String keyword) {
        Page<DockerServer> page;
        if (StringUtils.isEmpty(keyword)) {
            page = dockerServerDao.findAll(new SpringPageParamAdapter(pageParam));
        } else {
            page = dockerServerDao.findByNameLikeOrUrlLike('%' + keyword + '%', '%' + keyword + '%', new SpringPageParamAdapter(pageParam));
        }
        PageResult<DockerServer> pageResult = new SpringPageResultAdapter<>(page);
        return pageResult.convert(dockerServerEntityToView).convert(this::fill);
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        if (Boolean.TRUE.equals(rescheduleWhenStart)) {
            List<DockerServer> dockerServers = dockerServerDao.findAll();
            for (DockerServer dockerServer : dockerServers) {
                try {
                    scheduler.deleteJob(this.getJobKey(dockerServer.getId(), DockerServerHealthCheckJob.class));
                    scheduler.deleteJob(this.getJobKey(dockerServer.getId(), DockerServerStatJob.class));
                    scheduler.scheduleJob(
                            this.getJobDetail(dockerServer.getId(), DockerServerHealthCheckJob.class),
                            this.getJobTrigger(dockerServer.getId(), DockerServerHealthCheckJob.class, 600)
                    );
                    scheduler.scheduleJob(
                            this.getJobDetail(dockerServer.getId(), DockerServerStatJob.class),
                            this.getJobTrigger(dockerServer.getId(), DockerServerStatJob.class, 5)
                    );
                } catch (SchedulerException e) {
                    throw new IllegalStateException(e);
                }
            }
        }
    }
}
